package code.wrtp;

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import javax.swing.plaf.SliderUI;

import code.mobility.Position;
import code.network.AddressUtil;
import code.wrtp.packets.Packet;
import code.wrtp.packets.SolicitEntering;
import code.wrtp.packets.SolicitEnteringReply;

public class Station implements Runnable {

	private Node node;
	private Position position;
	private double speed;
	private double energy;
	private Sender sender;
	private Receiver receiver;
	private String subNet;
	private short port;
	private AddressUtil addressUtil;
	private volatile boolean isRunning;
	private Thread stationThread;
	private final short broadcastID = Constants.BROADCAST_ADDRESS;
	private Queue<SolicitEnteringReply> solicitReplyQueue;
	private ScheduledExecutorService scheduleTaskExecutor; /*
															 * Schedule tasks to
															 * be executed
															 */

	public Station() {
	}

	public Station(short ID, Position position, double speed, double energy,
			String subNet, short port) {

		this.position = position;
		this.speed = speed;
		this.energy = energy;
		this.subNet = subNet;
		this.port = port;

		addressUtil = new AddressUtil();
		node = new Node(ID);
		node.setState(State.ON);
		sender = new Sender(this);
		receiver = new Receiver(sender, this, subNet, port, node.getNodeID());
		solicitReplyQueue = new ConcurrentLinkedQueue<SolicitEnteringReply>();
		scheduleTaskExecutor = Executors
				.newScheduledThreadPool(Constants.NUMBER_OF_SCHEDULE);
	}

	public void startThread() {
		isRunning = true;
		receiver.startThread();
		sender.startSenderThread();
		stationThread = new Thread(this);
		stationThread.start();
		assert (node.getState() == State.ON);
		node.setPreviousState(State.ON);
		node.setState(State.RESET);
	}

	public void stopThread() {
		isRunning = false;
		receiver.stopThread();
		sender.stopSenderThread();
		stationThread.interrupt();
	}

	@Override
	public void run() {
		while (isRunning) {

		}

	}

	public short getPort() {
		return port;
	}

	public void setPort(short port) {
		this.port = port;
	}

	public Node getNode() {
		return node;
	}

	public void setNode(Node node) {
		this.node = node;
	}

	public Position getPosition() {
		return position;
	}

	public void setPosition(Position position) {
		this.position = position;
	}

	public double getSpeed() {
		return speed;
	}

	public void setSpeed(double speed) {
		this.speed = speed;
	}

	public double getEnergy() {
		return energy;
	}

	public void setEnergy(double energy) {
		this.energy = energy;
	}

	public Sender getSender() {
		return sender;
	}

	public void setSender(Sender sender) {
		this.sender = sender;
	}

	public Receiver getReceiver() {
		return receiver;
	}

	public void setReceiver(Receiver receiver) {
		this.receiver = receiver;
	}

	public String getSubNet() {
		return subNet;
	}

	public void setSubNet(String subNet) {
		this.subNet = subNet;
	}

	public AddressUtil getAddressUtil() {
		return this.addressUtil;
	}

	/* Methods of connections */

	/*
	 * It broadcasts a respective broadcast packet until We don't need enqueue
	 * these kind of packet, because there are instan
	 */
	private void solicitEntering() {
		int attemptingNumber = 0;

		Packet solicitEnteringPacketReply = null;
		Packet solicitEnteringPacket = new SolicitEntering(
				FrameControlType.SOLICIT_ENTERING, node.getNodeID(),
				broadcastID);

		try {
			while ((solicitEnteringPacketReply == null)
					&& (attemptingNumber < Constants.MAX_BROADCAST_ATTEMPTING)) {

				sender.broadcastPacket(solicitEnteringPacket);
				Thread.sleep(Constants.BROADCAST_RETRANSMISSION_TIME * 1000);
				++attemptingNumber;
				solicitEnteringPacketReply = solicitReplyQueue.poll();
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/*
	 * @param destineAddress - address (address of node that solicits entering)
	 * where to send the reply
	 * 
	 * @param senderFrameControlType - tell us whether packet is from a varal or
	 * a ring. To reply solicit entering from varal or a simple ring Reply could
	 * be done when station gets token.
	 */

	public void replyToSolicitEntering(short destineAddress,
			byte senderFrameControlType) {
		Packet packet;
		if (senderFrameControlType == FrameControlType.SOLICIT_ENTERING) {
			packet = new SolicitEnteringReply(
					FrameControlType.RING_ENTERING_REPLY, destineAddress,
					node.getNodeID(), node.getPredecessor());
		} else {/*
				 * The packet frameControlType is SOLICIT_ENTERING OR
				 * VARAL_ENTERING_REPLY. Must be checked in the method run()
				 */
			packet = new SolicitEnteringReply(
					FrameControlType.VARAL_ENTERING_REPLY, destineAddress,
					node.getNodeID(), node.getPredecessor());
		}

		sender.sendPacket(destineAddress, packet);
	}

	/* Add packet to the SolicitEnteringReply */
	public boolean addSolicitEnteringReplyPacket(byte fc, short da, short sa,
			short mySuccessorID) {
		return solicitReplyQueue.add(new SolicitEnteringReply(fc, da, sa,
				mySuccessorID));
	}

	/* Reset structures */
	private void reset() {
		node.setPreviousState(node.getState());
		node.setState(State.RESET);
		node.setPredecessor(node.getNodeID());
		node.setSuccessor(node.getNodeID());
		node.setRA(Short.valueOf("0"));
		node.setAmCoordinator(false);
		node.setCoordCnt(-1);
		node.setGenCnt(-1);
		node.setVA(Short.valueOf("0"));
		node.setVaralPredecessor(Short.valueOf("0"));
		node.setVaralSuccessor(Short.valueOf("0"));
	}

	/*
	 * Set ring's or varal's predecessor
	 */
	private void setPredecessor(short predecessor, byte fc) {
		if (fc == FrameControlType.SET_PREDECESSOR)
			node.setPredecessor(predecessor);
		else if (fc == FrameControlType.VARAL_SET_PREDECESSOR)
			node.setVaralPredecessor(predecessor);
	}
	
	/*Set ring's or varal's successor*/
	private void setSucessor(short successor, byte fc) {
		if (fc == FrameControlType.SET_SUCCESSOR)
			node.setSuccessor(successor);
		else if (fc == FrameControlType.VARAL_SET_SUCCESSOR)
			node.setVaralSuccessor(successor);
	}
}
